import sexprs
import reserved
from reserved import schemeReserved

class DoesntMatchSexpr(Exception):
    pass

class AbstractSchemeExpr(object):
    
    @staticmethod
    def parse(strToParse):
        sexpr, remaining = sexprs.AbstractSexpr.readFromString(strToParse)
        return AbstractSchemeExpr.TagParse(sexpr), remaining

    @staticmethod
    def TagParse(sexpr):
        if (QQuote.isQuote(sexpr,'quasiquote')):
            test = QQuote.qqexpander(sexpr.cdr().car())
            test = AbstractSchemeExpr.TagParse(test)
            return test
        if Constant.isConstant(sexpr):
            #print('Constant found ' + str(sexpr))
            return Constant(sexpr)
        elif Variable.isVariable(sexpr):
            #print('Variable found ' + str(sexpr))
            return Variable(sexpr)
        elif IfThenElse.isIf(sexpr):
            #print('IfThen found ' + str(sexpr))
            return IfThenElse.fromSexpr(sexpr)
        elif LambdaSimple.isLambdaSimple(sexpr):
            #print('LambdaSimple found ' + str(sexpr))
            return LambdaSimple.fromSexpr(sexpr)
        elif LambdaOpt.isLambdaOpt(sexpr):
            #print('LambdaOpt found ' + str(sexpr))
            return LambdaOpt.fromSexpr(sexpr)
        elif LambdaVar.isLambdaVar(sexpr):
            #print('LambdaVar found ' + str(sexpr))
            return LambdaVar.fromSexpr(sexpr)
        elif Or.isOr(sexpr):
            #print('Or found ' + str(sexpr))
            return Or.fromSexpr(sexpr)
        elif Def.isDef(sexpr):
            #print('Def found ' + str(sexpr))
            return Def.fromSexpr(sexpr)
        elif(Applic.isLet(sexpr)):
            #print('Let found' + str(sexpr))
            return AbstractSchemeExpr.TagParse(Applic.getApplicFromLet(sexpr))
        elif(Applic.isLetStar(sexpr)):
            #print('Let* found' + str(sexpr))
            test = Applic.getLetFromLetStar(sexpr)
            #print(test)
            return AbstractSchemeExpr.TagParse(test)
        elif(Applic.isLetrec(sexpr)):
            #print('Letrec found' + str(sexpr))
            test = Applic.getYagFromLetrec(sexpr)
            #print(test)
            return AbstractSchemeExpr.TagParse(test)
        elif Applic.isApplic(sexpr):
            #print('Applic found ' + str(sexpr))
            return Applic.fromSexpr(sexpr)
        else:
            print(sexpr)
            raise DoesntMatchSexpr

    @staticmethod
    def getTagParsedList(pairsList):
        #print('getTagParsedList' + str(pairsList))
        if (isinstance(pairsList,sexprs.Pair)):
            if (not isinstance(pairsList.cdr(),sexprs.Pair)):
                if (isinstance(pairsList.cdr(),sexprs.Nil)):
                    return [AbstractSchemeExpr.TagParse(pairsList.car())]
                else:
                    return [AbstractSchemeExpr.TagParse(pairsList.car()),AbstractSchemeExpr.TagParse(pairsList.cdr())]
            else:
                return [AbstractSchemeExpr.TagParse(pairsList.car())] + (AbstractSchemeExpr.getTagParsedList(pairsList.cdr()))
        else:
            return []
        
    @staticmethod
    def getList(pairsList):
        if (isinstance(pairsList,sexprs.Pair)):
            if (not isinstance(pairsList.cdr(),sexprs.Pair)):
                if (isinstance(pairsList.cdr(),sexprs.Nil)):
                    return [pairsList.car()]
                else:
                    return [pairsList.car(),pairsList.cdr()]
            else:
                return [pairsList.car()] + (AbstractSchemeExpr.getList(pairsList.cdr()))
        else:
            return []
        
    @staticmethod
    def getPairs(pythonList):
        pairsList = sexprs.Nil()
        for obj in reversed(pythonList):
            pairsList = sexprs.Pair(obj,pairsList)
        return pairsList

    def debruijn(self):
        return self.debruijnHelper([],[])

    def debruijnHelper(self,params,bounds):
        return self
    
    def annotateTC(self):
        return self.annotateTCHelper(True)
    
    def annotateTCHelper(self,isTC):
        return self
    
    def semantic_analysis(self):
        return self.debruijn().annotateTC()

    def code_gen(self)
        return ""
    
###################################################################################################################
class Constant(AbstractSchemeExpr):
    def __init__(self,constant):
        #Sets the constant value if match.
        if(Constant.isQuote(constant)):
            self.val = "'" + str(constant.cdr().car())
            self.isQuoted = True
        elif(Constant.isConstant(constant)):
            self.val = constant
            self.isQuoted = False
        else:
            raise DoesntMatchSexpr
            
    def __str__(self):
        return str(self.val)

    @staticmethod
    def isConstant(objToCheck):
        #Check for possible options.
        isVoid = isinstance(objToCheck,sexprs.Void)
        isBoolean = isinstance(objToCheck,sexprs.Boolean)
        isChar = isinstance(objToCheck,sexprs.Char)
        isAbstractNumber = isinstance(objToCheck,sexprs.AbstractNumber)
        isString = isinstance(objToCheck,sexprs.String)
        isNil = isinstance(objToCheck,sexprs.Nil)
        return (isBoolean or isChar or isAbstractNumber or isString or isVoid or Constant.isQuote(objToCheck) or isNil)
    
    @staticmethod
    def isQuote(objToCheck):
        #Check for possible options.
        isQuote = isinstance(objToCheck,sexprs.Pair) \
                    and isinstance(objToCheck.car(),sexprs.Symbol)\
                    and objToCheck.car().val in ['quote']\
                    and isinstance(objToCheck.cdr(),sexprs.Pair)
        return isQuote

###################################################################################################################
###################################################################################################################

class Variable(AbstractSchemeExpr):
    def __init__(self,variable):
        self.val = variable
        
    @staticmethod
    def fromSexpr(sexpr):
        if(Variable.isVariable(sexpr) and sexpr.val not in schemeReserved):
            return Variable(sexpr)
        else:
            raise DoesntMatchSexpr
        
    def __str__(self):
        return str(self.val)
   
    @staticmethod
    def isVariable(objToCheck):
        #Check for possible options.
        isSymbol = isinstance(objToCheck,sexprs.Symbol)
        return (isSymbol)

    def debruijnHelper(self,params,bounds):
        #print('asdasd')
        #print(','.join(list(map(str,params))))
        #print(str(self.val.val))
        #print(self.val.val in list(map(str,params)))
        if(self.val.val in params):
            return VarParam(self,params.index(self.val.val))
        else:
            index = 0
            for x in bounds:
                if(self.val.val in x):
                    return VarBound(self,index,x.index(self.val.val))
                else:
                    index = index + 1
        return VarFree(self)

###################################################################################################################

class VarFree(Variable):
    def __init__(self,variable):
        super().__init__(variable)
        
    def __str__(self):
        return str(self.val)

###################################################################################################################

class VarParam(Variable):
    def __init__(self,variable,minor):
        super().__init__(variable)
        self.minor = minor
        
    def __str__(self):
        return str(self.val)

###################################################################################################################

class VarBound(Variable):
    def __init__(self,variable,major,minor):
        super().__init__(variable)
        self.major = major
        self.minor = minor
        
    def __str__(self):
        return str(self.val)
        
###################################################################################################################
###################################################################################################################
class IfThenElse(AbstractSchemeExpr):
    def __init__(self,testTag,thenTag,elseTag):
        self.testTag = testTag
        self.thenTag = thenTag
        self.elseTag = elseTag
        
    @staticmethod
    def fromSexpr(sexpr):
        if(IfThenElse.isIfThen(sexpr)):
            testTag = AbstractSchemeExpr.TagParse(sexpr.cdr().car())
            thenTag = AbstractSchemeExpr.TagParse(sexpr.cdr().cdr().car())
            elseTag = Constant(sexprs.Void())
            return IfThenElse(testTag,thenTag,elseTag)
        elif(IfThenElse.isIfThenElse(sexpr)):
            testTag = AbstractSchemeExpr.TagParse(sexpr.cdr().car())
            thenTag = AbstractSchemeExpr.TagParse(sexpr.cdr().cdr().car())
            elseTag = AbstractSchemeExpr.TagParse(sexpr.cdr().cdr().cdr().car())
            return IfThenElse(testTag,thenTag,elseTag)
        elif(IfThenElse.isCond(sexpr)):
            return IfThenElse.getIfFromCond(sexpr.cdr()) #sexpr.cdr() skip on the 'cond'
        elif(IfThenElse.isAnd(sexpr)):
            return IfThenElse.getIfFromAnd(sexpr.cdr()) #sexpr.cdr() skip on the 'and'
        else:
            raise DoesntMatchSexpr
        
    def __str__(self):
        return '(if ' + str(self.testTag) + ' ' + str(self.thenTag) + ' ' + str(self.elseTag) + ')'

       
    @staticmethod
    def isIf(objToCheck):
        return (IfThenElse.isIfThen(objToCheck) \
                or IfThenElse.isIfThenElse(objToCheck) \
                or IfThenElse.isCond(objToCheck)\
                or IfThenElse.isAnd(objToCheck))
    
    @staticmethod
    def isIfThen(objToCheck):
        #Check for possible options.
        isIfThen = isinstance(objToCheck,sexprs.Pair) \
            and isinstance(objToCheck.car(),sexprs.Symbol) \
            and objToCheck.car().val in ['IF']\
            and isinstance(objToCheck.cdr(),sexprs.Pair)\
            and isinstance(objToCheck.cdr().cdr(),sexprs.Pair)\
            and isinstance(objToCheck.cdr().cdr().cdr(),sexprs.Nil)
        return (isIfThen)
    
    @staticmethod
    def isIfThenElse(objToCheck):
        #Check for possible options.
        isIfThen = isinstance(objToCheck,sexprs.Pair) \
            and isinstance(objToCheck.car(),sexprs.Symbol) \
            and objToCheck.car().val in ['IF']\
            and isinstance(objToCheck.cdr(),sexprs.Pair)\
            and isinstance(objToCheck.cdr().cdr(),sexprs.Pair)\
            and isinstance(objToCheck.cdr().cdr().cdr(),sexprs.Pair)\
            and isinstance(objToCheck.cdr().cdr().cdr().cdr(),sexprs.Nil)
        return (isIfThen)
    
    @staticmethod
    def isCond(objToCheck):
        #Check for possible options.
        isCond = isinstance(objToCheck,sexprs.Pair) \
            and isinstance(objToCheck.car(),sexprs.Symbol) \
            and objToCheck.car().val in ['COND']
        
        if isCond:
            #Make sure we have cond clauses
            currentClause = objToCheck.cdr()
            while IfThenElse.isCondClause(currentClause):
                currentClause = currentClause.cdr()
      
            isCond = isCond and isinstance(currentClause,sexprs.Nil)
            return (isCond)
        else:
            return isCond
    
    @staticmethod
    def isCondClause(objToCheck):
        #Check for possible options.
        isCondClauseBool = isinstance(objToCheck,sexprs.Pair) \
            and isinstance(objToCheck.car(),sexprs.Pair) \
            and isinstance(objToCheck.car().cdr(),sexprs.Pair) \
            and isinstance(objToCheck.car().cdr().cdr(),sexprs.Nil)
        return (isCondClauseBool)
        
    @staticmethod
    def isElseClause(objToCheck):
        #Check for possible options.
        isCondClauseBool = isinstance(objToCheck,sexprs.Pair) \
            and isinstance(objToCheck.car(),sexprs.Pair) \
            and isinstance(objToCheck.car().car(),sexprs.Symbol) \
            and objToCheck.car().car().val in ['ELSE'] \
            and isinstance(objToCheck.car().cdr(),sexprs.Pair) \
            and isinstance(objToCheck.car().cdr().cdr(),sexprs.Nil)
        return (isCondClauseBool)
    
    @staticmethod
    def getIfFromCond(condObject):
        if(IfThenElse.isElseClause(condObject)):
            return AbstractSchemeExpr.TagParse(condObject.car().cdr().car())
        else:
            #testTag = AbstractSchemeExpr.TagParse(condObject.car().car())
            #thenTag = AbstractSchemeExpr.TagParse(condObject.car().cdr().car())
            #elseTag = AbstractSchemeExpr.TagParse(IfThenElse.getIfFromCond(condObject.cdr()))
            return IfThenElse(AbstractSchemeExpr.TagParse(condObject.car().car()),\
                              AbstractSchemeExpr.TagParse(condObject.car().cdr().car()),\
                              IfThenElse.getIfFromCond(condObject.cdr()))

    @staticmethod
    def isAnd(objToCheck):
        #print(objToCheck)
        isAnd = isinstance(objToCheck,sexprs.Pair) and \
                (isinstance(objToCheck.car(),sexprs.Symbol)) and objToCheck.car().val in ['AND']
        return isAnd and (Or.isOrParameter(objToCheck.cdr()))


    @staticmethod
    def getIfFromAnd(andObject):
        #print(andObject)
        if(isinstance(andObject,sexprs.Nil)):
            return IfThenElse(AbstractSchemeExpr.TagParse(sexprs.Boolean(True)),\
                              AbstractSchemeExpr.TagParse(sexprs.Boolean(True)),
                              AbstractSchemeExpr.TagParse(sexprs.Boolean(True)))
        elif(isinstance(andObject,sexprs.Pair) and isinstance(andObject.cdr(),sexprs.Nil)):
            return IfThenElse(AbstractSchemeExpr.TagParse(andObject.car()),\
                                AbstractSchemeExpr.TagParse(andObject.car()),\
                                AbstractSchemeExpr.TagParse(sexprs.Boolean(False)))
        else:
            return IfThenElse(AbstractSchemeExpr.TagParse(andObject.car()),\
                              IfThenElse.getIfFromAnd(andObject.cdr()),\
                              AbstractSchemeExpr.TagParse(sexprs.Boolean(False)))
    
    def debruijnHelper(self,params,bounds):
        return IfThenElse(self.testTag.debruijnHelper(params,bounds),\
                          self.thenTag.debruijnHelper(params,bounds),\
                          self.elseTag.debruijnHelper(params,bounds))

    def annotateTCHelper(self,isTC):
        return IfThenElse(self.testTag,\
                          self.thenTag.annotateTCHelper(True),\
                          self.elseTag.annotateTCHelper(True))
    
###################################################################################################################
###################################################################################################################
class AbstractLambda(AbstractSchemeExpr):
    def __init__(self):
        pass
    def __str__(self):
        return 'AbstractLambda'
        
    @staticmethod
    def isLambda(objToCheck):
        #Check for possible options.
        isLambda = isinstance(objToCheck,sexprs.Pair) and \
                    ((isinstance(objToCheck.car(),sexprs.Symbol) and objToCheck.car().val in ['LAMBDA']) or \
                     (isinstance(objToCheck.car(),sexprs.Char) and objToCheck.car().val == chr(0x03bb)))
        if isLambda:
            isLambdaHasBody = (isinstance(objToCheck.cdr(),sexprs.Pair) and isinstance(objToCheck.cdr().cdr(),sexprs.Pair))
            isLambdaParams = LambdaSimple.isLambdaSimpleParameter(objToCheck.cdr().car()) \
                             or LambdaOpt.isLambdaOptParameter(objToCheck.cdr().car())\
                             or LambdaVar.isLambdaVarParameter(objToCheck.cdr().car())
        else:
            return False
        return (isLambda and isLambdaParams and isLambdaHasBody)

###################################################################################################################
###################################################################################################################    
class LambdaSimple(AbstractLambda):
    def __init__(self,varList,body):
        self.varList = varList
        self.body = body
    
    @staticmethod
    def fromSexpr(sexpr):
        if(AbstractLambda.isLambda(sexpr) and LambdaSimple.isLambdaSimpleParameter(sexpr.cdr().car())):
            varList = AbstractSchemeExpr.getTagParsedList(sexpr.cdr().car())
            body = AbstractSchemeExpr.TagParse(sexpr.cdr().cdr().car())
            return LambdaSimple(varList,body)
        else:
            raise DoesntMatchSexpr
        
    def __str__(self):
        return '(lambda (' + str(' '.join(map(str, self.varList))) + ') ' +str(self.body) + ')'

    @staticmethod
    def isLambdaSimple(objToCheck):
        return AbstractLambda.isLambda(objToCheck) and LambdaSimple.isLambdaSimpleParameter(objToCheck.cdr().car())
    
    @staticmethod
    def isLambdaSimpleParameter(parametersExpr):
        #Check for possible options.
        if (isinstance(parametersExpr,sexprs.Nil)):
            return True
        if (isinstance(parametersExpr,sexprs.Pair)):
            if (isinstance(parametersExpr.cdr(),sexprs.Nil)):
                return isinstance(parametersExpr.car(),sexprs.Symbol)
            else:
                return isinstance(parametersExpr.car(),sexprs.Symbol) and LambdaSimple.isLambdaSimpleParameter(parametersExpr.cdr())
        return False

    
    def debruijnHelper(self,params,bounds):
        return LambdaSimple(self.varList,\
                            self.body.debruijnHelper(list(map(str,self.varList)),[params] + bounds))

    def annotateTCHelper(self,isTC):
        return LambdaSimple(self.varList,\
                          self.body.annotateTCHelper(True))
    
###################################################################################################################
###################################################################################################################    
class LambdaVar(AbstractLambda):
    def __init__(self,varList,body):
        self.varList = varList
        self.body = body
    
    @staticmethod
    def fromSexpr(sexpr):
        if(AbstractLambda.isLambda(sexpr) and LambdaVar.isLambdaVarParameter(sexpr.cdr().car())):
            varList = AbstractSchemeExpr.TagParse(sexpr.cdr().car())
            body = AbstractSchemeExpr.TagParse(sexpr.cdr().cdr().car())
            return LambdaVar(varList,body)
        else:
            raise DoesntMatchSexpr

    def __str__(self):
        return '(lambda ' + str(self.varList) + ' ' + str(self.body) + ')'
    
    @staticmethod
    def isLambdaVar(objToCheck):
        return AbstractLambda.isLambda(objToCheck) and LambdaVar.isLambdaVarParameter(objToCheck.cdr().car())
    
    @staticmethod
    def isLambdaVarParameter(parametersExpr):
        #Check for possible options.
        return (isinstance(parametersExpr,sexprs.Symbol))

    def debruijnHelper(self,params,bounds):
        return LambdaVar(self.varList,\
                        self.body.debruijnHelper([str(self.varList)],[params] + bounds))

    def annotateTCHelper(self,isTC):
        return LambdaVar(self.varList,\
                          self.body.annotateTCHelper(True))
###################################################################################################################
###################################################################################################################    
class LambdaOpt(AbstractLambda):
    
    def __init__(self,varList,body):
        self.varList = varList
        self.body = body
    
    @staticmethod
    def fromSexpr(sexpr):
        if(AbstractLambda.isLambda(sexpr) and LambdaOpt.isLambdaOptParameter(sexpr.cdr().car())):
            varList = AbstractSchemeExpr.getTagParsedList(sexpr.cdr().car())
            body = AbstractSchemeExpr.TagParse(sexpr.cdr().cdr().car())
            return LambdaOpt(varList,body)
        else:
            raise DoesntMatchSexpr

    def __str__(self):
        return '(lambda (' + str(' '.join(map(str, self.varList[:-1]))) + ' . ' + str(self.varList[-1]) +') ' + str(self.body) + ')' #Assume there is at list 2 items in the list.
    
    @staticmethod
    def isLambdaOpt(objToCheck):
        return AbstractLambda.isLambda(objToCheck) and LambdaOpt.isLambdaOptParameter(objToCheck.cdr().car())
    
    @staticmethod
    def isLambdaOptParameter(parametersExpr):
        #Check for possible options.
        if (isinstance(parametersExpr,sexprs.Pair)):
            if (isinstance(parametersExpr.cdr(),sexprs.Symbol)):
                return isinstance(parametersExpr.car(),sexprs.Symbol)
            else:
                return isinstance(parametersExpr.car(),sexprs.Symbol) and LambdaOpt.isLambdaOptParameter(parametersExpr.cdr())
        return False
    
    def debruijnHelper(self,params,bounds):
        return LambdaOpt(self.varList,\
                        self.body.debruijnHelper(list(map(str,self.varList)),[params] + bounds))

    def annotateTCHelper(self,isTC):
        return LambdaOpt(self.varList,\
                          self.body.annotateTCHelper(True))
    
###################################################################################################################
###################################################################################################################    
class Applic(AbstractSchemeExpr):
    def __init__(self,functionName,params):
        self.functionName = functionName
        self.params = params
        
    def __str__(self):
        return '(' + str(self.functionName) + ' ' + str(' '.join(map(str, self.params))) + ')'
    
    @staticmethod
    def fromSexpr(sexpr):
        if(Applic.isApplic(sexpr)):
            functionName = AbstractSchemeExpr.TagParse(sexpr.car())
            params = AbstractSchemeExpr.getTagParsedList(sexpr.cdr())
            return Applic(functionName,params)
        else:
            raise DoesntMatchSexpr
        
    @staticmethod
    def isApplic(objToCheck):
        isApplic = isinstance(objToCheck,sexprs.Pair)
        isApplicContent = isApplic and not (isinstance(objToCheck.car(),sexprs.Nil))

        return isApplic and isApplicContent and (Applic.isApplicParameter(objToCheck.cdr()))
    
    @staticmethod
    def isLet(objToCheck):
        isLet = isinstance(objToCheck,sexprs.Pair)
        isLetSymbol = isLet and (isinstance(objToCheck.car(),sexprs.Symbol)) and objToCheck.car().val in ['LET']
        isBody = isLet and isLetSymbol and (isinstance(objToCheck.cdr(),sexprs.Pair) and isinstance(objToCheck.cdr().cdr(),sexprs.Pair))
        return isLet and isLetSymbol and isBody and (Applic.isLetParameter(objToCheck.cdr().car()))

    @staticmethod
    def isLetStar(objToCheck):
        isLet = isinstance(objToCheck,sexprs.Pair)
        isLetSymbol = isLet and (isinstance(objToCheck.car(),sexprs.Symbol)) and objToCheck.car().val in ['LET*']
        isBody = isLet and isLetSymbol and (isinstance(objToCheck.cdr(),sexprs.Pair) and isinstance(objToCheck.cdr().cdr(),sexprs.Pair))
        return isLet and isLetSymbol and isBody and (Applic.isLetParameter(objToCheck.cdr().car()))
    
    @staticmethod
    def isLetrec(objToCheck):
        isLet = isinstance(objToCheck,sexprs.Pair)
        isLetSymbol = isLet and (isinstance(objToCheck.car(),sexprs.Symbol)) and objToCheck.car().val in ['LETREC']
        isBody = isLet and isLetSymbol and (isinstance(objToCheck.cdr(),sexprs.Pair) and isinstance(objToCheck.cdr().cdr(),sexprs.Pair))
        return isLet and isLetSymbol and isBody and (Applic.isLetParameter(objToCheck.cdr().car()))
    
    @staticmethod
    def isApplicParameter(parametersExpr):
        #Check for possible options.
        if (isinstance(parametersExpr,sexprs.Nil)):
            return True
        if (isinstance(parametersExpr,sexprs.Pair)):
            return Applic.isApplicParameter(parametersExpr.cdr())
        return False

    @staticmethod
    def isLetParameter(parametersExpr):
        #Check for possible options.
        if (isinstance(parametersExpr,sexprs.Nil)):
            return True
        if (isinstance(parametersExpr,sexprs.Pair)):
            varPair = parametersExpr.car()
            isLetPair = isinstance(varPair,sexprs.Pair) and Variable.isVariable(varPair.car()) and isinstance(varPair.cdr(),sexprs.Pair) and isinstance(varPair.cdr().cdr(),sexprs.Nil)
            return isLetPair and Applic.isLetParameter(parametersExpr.cdr())
        return False
    
    @staticmethod
    def getApplicFromLet(letExpr):
        return sexprs.Pair(sexprs.Pair(sexprs.Symbol('LAMBDA'),\
                            sexprs.Pair(AbstractSchemeExpr.getPairs(list(map(sexprs.Pair.car,AbstractSchemeExpr.getList(letExpr.cdr().car())))),\
                            sexprs.Pair(letExpr.cdr().cdr().car(),sexprs.Nil()))),\
                AbstractSchemeExpr.getPairs(list(map(sexprs.Pair.car,(list(map(sexprs.Pair.cdr,AbstractSchemeExpr.getList(letExpr.cdr().car()))))))))
    
    @staticmethod
    def getLetFromLetStar(letStarExpr):
        return Applic.getLetFromLetStarParameters(letStarExpr.cdr().car(),letStarExpr.cdr().cdr().car())
        
    @staticmethod
    def getLetFromLetStarParameters(letExpr, body):
        if(isinstance(letExpr,sexprs.Nil)):
            return body
        else:
            return sexprs.Pair(sexprs.Symbol('LET'),\
                               sexprs.Pair(sexprs.Pair(letExpr.car(),sexprs.Nil()),\
                               sexprs.Pair(Applic.getLetFromLetStarParameters(letExpr.cdr(),body),sexprs.Nil())))

    @staticmethod
    def getYagFromLetrec(letrecExpr):
        pairsExpr = letrecExpr.cdr().car()
        paramsList = list(map(sexprs.Pair.car,AbstractSchemeExpr.getList(letrecExpr.cdr().car())))
        freshParam = sexprs.Symbol('g0' + ''.join(list(map(str,paramsList))))
        paramsList = [freshParam] + paramsList
        lambdaParams = AbstractSchemeExpr.getPairs(paramsList)
        lambdaExpres = list(map(sexprs.Pair.car,(list(map(sexprs.Pair.cdr,AbstractSchemeExpr.getList(letrecExpr.cdr().car()))))))
        lambdaList = [sexprs.Pair(sexprs.Symbol('LAMBDA'),sexprs.Pair(lambdaParams,sexprs.Pair(letrecExpr.cdr().cdr().car(),sexprs.Nil())))]
        for expr in lambdaExpres:
            lambdaList.append(sexprs.Pair(sexprs.Symbol('LAMBDA'),sexprs.Pair(lambdaParams,sexprs.Pair(expr,sexprs.Nil()))))
        return sexprs.Pair(sexprs.Symbol('YAG'),AbstractSchemeExpr.getPairs(lambdaList))
                         
    
    def debruijnHelper(self,params,bounds):
        return Applic(self.functionName.debruijnHelper(params,bounds),\
                      (list(map(lambda x: x.debruijnHelper(params,bounds),self.params))))

    def annotateTCHelper(self,isTC):
        if(isTC):
            return ApplicTP(self.functionName.annotateTCHelper(False),\
                        (list(map(lambda x: x.annotateTCHelper(False),self.params))))
        else:
            return Applic(self.functionName.annotateTCHelper(False),\
                        (list(map(lambda x: x.annotateTCHelper(False),self.params))))
    
###################################################################################################################


class ApplicTP(Applic):
    def __init__(self,functionName,params):
        super().__init__(functionName,params)
    def __str__(self):
        return '(' + str(self.functionName) + ' ' + str(' '.join(map(str, self.params))) + ')'

        
###################################################################################################################
###################################################################################################################
class Or(AbstractSchemeExpr) :

    def __init__(self,orParams):
        self.orParams = orParams

    @staticmethod
    def fromSexpr(sexpr):
        if(Or.isOr(sexpr)):
            orParams = AbstractSchemeExpr.getTagParsedList(sexpr.cdr())
            return Or(orParams)
        else:
            raise DoesntMatchSexpr
        
    def __str__(self):
        return '(or ' + str(' '.join(map(str, self.orParams))) + ')'
    
    @staticmethod
    def isOr(objToCheck):
        isOr = isinstance(objToCheck,sexprs.Pair) and \
                (isinstance(objToCheck.car(),sexprs.Symbol)) and objToCheck.car().val in ['OR']
        return isOr and (Or.isOrParameter(objToCheck.cdr()))
    
    @staticmethod
    def isOrParameter(parametersExpr):
        #Check for possible options.
        if (isinstance(parametersExpr,sexprs.Nil)):
            return True
        if (isinstance(parametersExpr,sexprs.Pair)):
            return Applic.isApplicParameter(parametersExpr.cdr())
        return False
    
    def debruijnHelper(self,params,bounds):
        return Or(list(map(lambda x: x.debruijnHelper(params,bounds),self.orParams)))
    
    def annotateTCHelper(self,isTC):
        return Or(list(map(lambda x: x.annotateTCHelper(False),self.orParams[:-1]))+[self.orParams[-1].annotateTCHelper(True)])

    
###################################################################################################################
###################################################################################################################    
class Def(AbstractSchemeExpr):
    def __init__(self,varName,varVal):
        self.varName = varName
        self.varVal = varVal
        
    @staticmethod
    def fromSexpr(sexpr):
        if(Def.isSimpleDef(sexpr)):
            varName = AbstractSchemeExpr.TagParse(sexpr.cdr().car())
            varVal = AbstractSchemeExpr.TagParse(sexpr.cdr().cdr().car())
            return Def(varName,varVal)
        elif(Def.isMITDef(sexpr)):
            varName = AbstractSchemeExpr.TagParse(sexpr.cdr().car().car())
            varVal = AbstractSchemeExpr.TagParse(sexprs.Pair(sexprs.Symbol('LAMBDA'),sexprs.Pair(sexpr.cdr().car().cdr(),sexprs.Pair(sexpr.cdr().cdr().car(),sexprs.Nil))))
            return Def(varName,varVal)
        else:
            raise DoesntMatchSexpr
        
    @staticmethod
    def isDef(objToCheck):
        return Def.isSimpleDef(objToCheck) or Def.isMITDef(objToCheck)
    
    @staticmethod
    def isSimpleDef(objToCheck):
        isSimpleDef = isinstance(objToCheck,sexprs.Pair)\
                      and isinstance(objToCheck.car(),sexprs.Symbol) and objToCheck.car().val in ['DEFINE']\
                      and isinstance(objToCheck.cdr(),sexprs.Pair) and Variable.isVariable(objToCheck.cdr().car())\
                      and isinstance(objToCheck.cdr(),sexprs.Pair)
        return isSimpleDef

    @staticmethod
    def isMITDef(objToCheck):
        isMITDef = isinstance(objToCheck,sexprs.Pair)\
                      and isinstance(objToCheck.car(),sexprs.Symbol) and objToCheck.car().val in ['DEFINE']\
                      and isinstance(objToCheck.cdr(),sexprs.Pair) and isinstance(objToCheck.cdr().car(),sexprs.Pair) and Variable.isVariable(objToCheck.cdr().car().car())\
                      and (LambdaVar.isLambdaVarParameter(objToCheck.cdr().car().cdr()) or LambdaOpt.isLambdaOptParameter(objToCheck.cdr().car().cdr()) or LambdaSimple.isLambdaSimpleParameter(objToCheck.cdr().car().cdr())) \
                      and not isinstance(objToCheck.cdr(),sexprs.Nil)
        return isMITDef
    
    def __str__(self):
        return '(define ' + str(self.varName) + ' ' + str(self.varVal) + ')'

    
    def debruijnHelper(self,params,bounds):
        return Def(self.varName.debruijnHelper(params,bounds),self.varVal.debruijnHelper(params,bounds))
    
    def annotateTCHelper(self,isTC):
        return Def(self.varName,self.varVal.annotateTCHelper(False))

###################################################################################################################
###################################################################################################################    
class QQuote(AbstractSchemeExpr):

    @staticmethod
    def qqexpander(sexpr):
        if(QQuote.isQuote(sexpr,'unquote')):
            return sexpr.cdr().car()
        elif(QQuote.isQuote(sexpr,'unquote-splicing')):
            print('unquote-splicing')
            print(sexpr)
            raise DoesntMatchSexpr
        elif (isinstance(sexpr,sexprs.Pair)):
            a = sexpr.car()
            b = sexpr.cdr()
            if (QQuote.isQuote(a,'unquote-splicing')):
                return sexprs.Pair(sexprs.Symbol('APPEND'),sexprs.Pair(a.cdr().car(),sexprs.Pair(QQuote.qqexpander(b),sexprs.Nil())))
            elif (QQuote.isQuote(b,'unquote-splicing')):
                return sexprs.Pair(sexprs.Symbol('CONS'),sexprs.Pair(QQuote.qqexpander(a),sexprs.Pair(b.cdr().car(),sexprs.Nil())))
            else:
                return sexprs.Pair(sexprs.Symbol('CONS'),sexprs.Pair(QQuote.qqexpander(a),sexprs.Pair(QQuote.qqexpander(b),sexprs.Nil())))
        elif (isinstance(sexpr,sexprs.Vector)):
            print('LIST->VECTOR')
            return sexprs.Pair(sexprs.Symbol('LIST->VECTOR'),sexprs.Pair(QQuote.qqexpander(AbstractSchemeExpr.getPairs(sexpr.vals)),sexprs.Nil()))
        elif (isinstance(sexpr,sexprs.Symbol) or isinstance(sexpr,sexprs.Nil)):
            return sexprs.Pair(sexprs.Symbol('quote'),sexprs.Pair(sexpr,sexprs.Nil()))
        else:
            return sexpr

    @staticmethod
    def isQuote(objToCheck,tag):
        val =  isinstance(objToCheck,sexprs.Pair) and \
                isinstance(objToCheck.car(),sexprs.Symbol) and objToCheck.car().val == tag and \
                isinstance(objToCheck.cdr(),sexprs.Pair) and isinstance(objToCheck.cdr().cdr(),sexprs.Nil)
        return val
